﻿using System.Collections.Generic;
using System.Threading.Tasks;
using Deer;
using Deer.Collections;
using Deer.Log;
using Deer.Util;

namespace Telex;

abstract class ARpcService : IRpcService
{
    public ARpcService(ILogger logger, ProtocolMap protocolMap, ActionAsync<RpcHost, ServiceKind> channelActiveFunc, ActionAsync<RpcHost, ServiceKind> channelInactiveFunc)
    {
        this.Logger = logger;
        this.protocolMap = protocolMap;
        this.channelActiveFunc = channelActiveFunc;
        this.channelInactiveFunc = channelInactiveFunc;
    }
    
    private ProtocolMap protocolMap;
    private ConcurrentMap<RpcHost, RpcChannel> channelMap = new();
    private ActionAsync<RpcHost, ServiceKind> channelActiveFunc;
    private ActionAsync<RpcHost, ServiceKind> channelInactiveFunc;
    
    private object sync = new object();

    public ILogger Logger { get; }

    protected abstract ServiceKind CurrentKind { get; }

    public IRpcChannel GetChannel(RpcHost host)
    {
        return this.channelMap.GetValueOrDefault(host);
    }
    
    protected void OnConnectEvent(ARcpConnection connect)
    {
        var remoteHost = connect.RemoteHost;
        lock (sync)
        {
            if (!channelMap.TryGetValue(remoteHost, out var channel))
            {
                channel = new RpcChannel(connect.LocalHost, remoteHost, this.Logger, this.protocolMap);
                channelMap.TryAdd(remoteHost, channel);
                channel.Start();
                this.OnChannelActive(channel);
            }
            
            channel.AddPipeline(new RpcPipeline(connect, CurrentKind == ServiceKind.CLIENT));
        }
    }
    
    protected void OnDisconnectEvent(ARcpConnection connect)
    {
        var remoteHost = connect.RemoteHost;
        lock (sync)
        {
            if (!this.channelMap.TryGetValue(remoteHost, out var channel))
            {
                this.Logger.Warn("The specified channel was not found. remoteHost:'" + remoteHost + "'");
                return;
            }
            channel.DelPipeline(connect.Id);
            if (channel.PipelineCount < 1)
            {
                this.OnChannelInactive(channel);
            }
        }
    }

    private void OnChannelActive(RpcChannel channel)
    {
        this.channelActiveFunc?.Invoke(channel.RemoteHost, this.CurrentKind);
    }

    private void OnChannelInactive(RpcChannel channel)
    {
        var remoteHost = channel.RemoteHost;
        if (!this.channelMap.TryRemove(remoteHost, out _))
        {
            this.Logger.Warn("The specified channel was not found. remoteHost:'" + remoteHost + "'");
            return;
        }
        
        channel.Release();

        this.channelInactiveFunc?.Invoke(remoteHost, this.CurrentKind);
    }

    public virtual ValueTask StopAsync()
    {
        foreach (var item in this.channelMap)
        {
            item.Value.Release();
        }
        
        this.channelMap.Clear();
        
        return ValueTaskUtil.CompletedTask;
    }
}